
package com.zhike.service.im.client.core;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observer;
import java.util.concurrent.atomic.AtomicLong;

import javax.swing.Timer;

import com.zhike.service.im.client.ClientCoreSDK;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class KeepAliveDaemon {

	private static KeepAliveDaemon instance = null;

	public static int KEEP_ALIVE_INTERVAL = 15000;
	public static int NETWORK_CONNECTION_TIME_OUT = KEEP_ALIVE_INTERVAL + 5000;
	public static int NETWORK_CONNECTION_TIME_OUT_CHECK_INTERVAL = 2 * 1000;

	private boolean keepAliveRunning = false;
	private AtomicLong lastGetKeepAliveResponseFromServerTimstamp = new AtomicLong(0);
	private Observer networkConnectionLostObserver = null;

	private boolean keepAliveTaskExcuting = false;
	private boolean keepAliveWillStop = false;
	private Timer keepAliveTimer = null;

	private Timer keepAliveTimeoutTimer = null;

	public static KeepAliveDaemon getInstance() {
		if (instance == null) {
			synchronized (KeepAliveDaemon.class) {
				if (instance == null) {
					instance = new KeepAliveDaemon();
				}
			}
		}
		return instance;
	}

	private KeepAliveDaemon() {
		init();
	}

	private void init() {
		keepAliveTimer = new Timer(KEEP_ALIVE_INTERVAL, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				doKeepAlive();
			}
		});

		keepAliveTimeoutTimer = new Timer(NETWORK_CONNECTION_TIME_OUT_CHECK_INTERVAL, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (ClientCoreSDK.DEBUG)
					log.debug("{}", "【IMCORE-TCP】心跳[超时检查]线程执行中...");

				doTimeoutCheck();
			}
		});
	}

	private void doKeepAlive() {
		if (!keepAliveTaskExcuting) {
			// boolean willStop = false;
			keepAliveTaskExcuting = true;
			if (ClientCoreSDK.DEBUG)
				log.info("{}", "【IMCORE-TCP】心跳包[发送]线程执行中...");
			int code = LocalDataSender.getInstance().sendKeepAlive();
			
			boolean isInitialedForKeepAlive = isInitialedForKeepAlive();
			if (isInitialedForKeepAlive)
				lastGetKeepAliveResponseFromServerTimstamp.set(System.currentTimeMillis());

			keepAliveTaskExcuting = false;
			if (!keepAliveWillStop) {
				; // do nothing
			} else {
				keepAliveTimer.stop();
			}
		}
	}

	private void doTimeoutCheck() {
		boolean isInitialedForKeepAlive = isInitialedForKeepAlive();
		if (!isInitialedForKeepAlive) {
			long now = System.currentTimeMillis();

			// TODO: just for debug
			// if(ClientCoreSDK.DEBUG)
			// log.info(TAG, ">>>> t1="+now+",
			// t2="+lastGetKeepAliveResponseFromServerTimstamp+" -> 差："+(now -
			// lastGetKeepAliveResponseFromServerTimstamp.longValue()));

			if (now - lastGetKeepAliveResponseFromServerTimstamp.longValue() >= NETWORK_CONNECTION_TIME_OUT) {
				notifyConnectionLost();
				keepAliveWillStop = true;
			}
		}
	}

	private boolean isInitialedForKeepAlive() {
		return (lastGetKeepAliveResponseFromServerTimstamp.longValue() == 0);
	}

	public void notifyConnectionLost() {
		stop();
		if (networkConnectionLostObserver != null)
			networkConnectionLostObserver.update(null, null);
	}

	public void stop() {
		if (keepAliveTimeoutTimer != null)
			keepAliveTimeoutTimer.stop();

		if (keepAliveTimer != null)
			keepAliveTimer.stop();

		keepAliveRunning = false;
		keepAliveWillStop = false;
		lastGetKeepAliveResponseFromServerTimstamp.set(0);
	}

	public void start(boolean immediately) {
		stop();

		if (immediately)
			keepAliveTimer.setInitialDelay(0);
		else
			keepAliveTimer.setInitialDelay(KEEP_ALIVE_INTERVAL);
		keepAliveTimer.start();

		if (immediately)
			keepAliveTimeoutTimer.setInitialDelay(0);
		else
			keepAliveTimeoutTimer.setInitialDelay(NETWORK_CONNECTION_TIME_OUT_CHECK_INTERVAL);
		keepAliveTimeoutTimer.start();

		keepAliveRunning = true;
		keepAliveWillStop = false;
	}

	public boolean isKeepAliveRunning() {
		return keepAliveRunning;
	}

	public void updateGetKeepAliveResponseFromServerTimstamp() {
		lastGetKeepAliveResponseFromServerTimstamp.set(System.currentTimeMillis());
	}

	public void setNetworkConnectionLostObserver(Observer networkConnectionLostObserver) {
		this.networkConnectionLostObserver = networkConnectionLostObserver;
	}
}
